#include "Stdafx.h"
#include "GDIUtils.h"
#include "Vertex.h"
#include "Polygon.h"

extern		COLORREF			g_Colors[DHC_SIZE];
extern		unsigned int		g_Sizes[DHC_SIZE];
extern		CPoint				g_Origin;


///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
CGDIUtils::CGDIUtils()
{
}

///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
CGDIUtils::~CGDIUtils()
{
}

///////////////////////////////////////////////////////////////////////////////////////////////////
// draw a line
///////////////////////////////////////////////////////////////////////////////////////////////////
void CGDIUtils::DrawLine( CDC *pDC, CPoint From, CPoint To )
{
    pDC->MoveTo(From);
    pDC->LineTo(To);
}

///////////////////////////////////////////////////////////////////////////////////////////////////
// draw a line
///////////////////////////////////////////////////////////////////////////////////////////////////
void CGDIUtils::DrawLine( CDC *pDC, CPoint From, CPoint To, COLORREF color )
{
	CPen pen, *pOldPen = NULL;
	pen.CreatePen( PS_SOLID, 2, color );
	pOldPen = pDC->SelectObject( &pen );
    pDC->MoveTo(From);
    pDC->LineTo(To);
	if( pOldPen )
		pDC->SelectObject( pOldPen );
}

///////////////////////////////////////////////////////////////////////////////////////////////////
// draw axis
///////////////////////////////////////////////////////////////////////////////////////////////////
void CGDIUtils::DrawAxis( CDC *pDC )
{
	CPen penDot(PS_SOLID, g_Sizes[DHC_AXIS], g_Colors[DHC_AXIS]);
	CGDISelectObject<CPen> tmpPen(pDC, &penDot);

	int x = g_Origin.x, y = g_Origin.y ;
	int width = 1000, height = 600;		// the length of x axis and y axis, determined by the resolution of the screen
	int right = x+width, top = y-height;

	int ArrowSizeX = 15, ArrowSizeY = 7;	// arrow size
	DrawLine( pDC, g_Origin, CPoint(right, y ) );
	DrawLine( pDC, CPoint( right,y ), CPoint( right- ArrowSizeX, y - ArrowSizeY) );
	DrawLine( pDC, CPoint( right,y ), CPoint( right- ArrowSizeX, y + ArrowSizeY) );
	DrawLine( pDC, g_Origin, CPoint(x, top ) );
	DrawLine( pDC, CPoint( x, top ), CPoint( x-ArrowSizeY, top+ArrowSizeX) );
	DrawLine( pDC, CPoint( x, top ), CPoint( x+ArrowSizeY, top+ArrowSizeX) );

	int FontSizeX = 10, FontSizeY = 20;		// font size
	DrawText( pDC, CPoint( x-FontSizeX, y ),"O", "Arial", FontSizeX, FontSizeY, g_Colors[DHC_AXIS] );
	DrawText( pDC, CPoint( x-FontSizeX/2-5, top-FontSizeY ),"Y", "Arial", FontSizeX, FontSizeY, g_Colors[DHC_AXIS] );
	DrawText( pDC, CPoint( right+5, y-FontSizeY/2 ),"X", "Arial", FontSizeX, FontSizeY, g_Colors[DHC_AXIS] );
}


///////////////////////////////////////////////////////////////////////////////////////////////////
// draw a rect
///////////////////////////////////////////////////////////////////////////////////////////////////
void CGDIUtils::DrawRubberRect( CDC *pDC, CPoint From, CPoint To )
{
	int x1=From.x, y1=From.y;
	int x2=To.x, y2=To.y;

	//!!! why the dotted line can not be shown?
	// set dotted pen
	CPen penDot(PS_DOT, g_Sizes[DHC_RUBBER], g_Colors[DHC_RUBBER]);
	CGDISelectObject<CPen> tmpPen(pDC, &penDot);

	pDC->MoveTo(From);
	pDC->LineTo( CPoint(x1, y2) );
	pDC->LineTo(To);
	pDC->LineTo( CPoint(x2, y1) );
	pDC->LineTo(From);
}


///////////////////////////////////////////////////////////////////////////////////////////////////
// draw a text at specified position
///////////////////////////////////////////////////////////////////////////////////////////////////
void CGDIUtils::DrawText(	CDC *pDC, 
							CPoint Location, 
							CString strText, 
							CString strFontName,
							int nFontWidth, 
							int nFontHeight, 
							COLORREF crFontColour, 
							long FontWeight)
{
	pDC->SetBkMode(TRANSPARENT);

	int nCharCount = strText.GetLength();

	//
	// Set the font:
	//
	CFont font;
	LOGFONT lf;										//Stores this fonts LogFont for quick retrieval
	lf.lfHeight			= nFontHeight;
	lf.lfWidth			= nFontWidth;
	lf.lfEscapement		= 0;
	lf.lfOrientation	= 0;
	lf.lfWeight			= FontWeight;				//FW_NORMAL;
	lf.lfItalic			= 0;
	lf.lfUnderline		= 0;
	lf.lfStrikeOut		= 0;
	lf.lfCharSet		= ANSI_CHARSET;
	lf.lfOutPrecision	= OUT_DEFAULT_PRECIS;
	lf.lfClipPrecision	= CLIP_DEFAULT_PRECIS;
	lf.lfQuality		= ANTIALIASED_QUALITY;
	lf.lfPitchAndFamily	= VARIABLE_PITCH | FF_ROMAN;
	
	strcpy( lf.lfFaceName, LPCTSTR(strFontName) );
	
	font.CreateFontIndirect(&lf);

	// do not need to restore the old Device Context any more
	//CFont* pOldFont = pDC->SelectObject(&font);
	CGDISelectObject<CFont> tmpFont(pDC, &font);    // set the font

	CSize size;
	GetTextExtentPoint32(	pDC->m_hDC,				// handle to device context
							(LPCTSTR)strText,		// pointer to text string
							strText.GetLength(),	// number of characters in string
							&size);					// pointer to structure for string size

	CRect TextRect(Location, size);

	//pDC->SetTextColor( crFontColour );
	CGDISetTextColor tmpTextColor( pDC, crFontColour );
	pDC->DrawText(strText, &TextRect, DT_LEFT);		//  << The Text DrawText Command >>


	// do not need to reset the Device Context any more
	//pDC->SelectObject(pOldFont);
}


///////////////////////////////////////////////////////////////////////////////////////////////////
// draw a text within specified rectangular region
///////////////////////////////////////////////////////////////////////////////////////////////////
void CGDIUtils::DrawText(	CDC *pDC, 
							CRect rect,
							CString strText, 
							CString strFontName,
							COLORREF crFontColour, 
							long FontWeight)
{
	pDC->SetBkMode(TRANSPARENT);

	if( rect.Width() == 0 || rect.Height() == 0 )
		return ;

	int nCharCount = strText.GetLength();

	// the height and width of the font
	int nFontHeight = rect.Height();
	int nFontWidth = rect.Width()/nCharCount*2/3;

	//
	// Set the font:
	//
	CFont font;
	LOGFONT lf;										//Stores this fonts LogFont for quick retrieval
	lf.lfHeight			= nFontHeight;
	lf.lfWidth			= nFontWidth;
	lf.lfEscapement		= 0;
	lf.lfOrientation	= 0;
	lf.lfWeight			= FontWeight;				//FW_NORMAL;
	lf.lfItalic			= 0;
	lf.lfUnderline		= 0;
	lf.lfStrikeOut		= 0;
	lf.lfCharSet		= ANSI_CHARSET;
	lf.lfOutPrecision	= OUT_DEFAULT_PRECIS;
	lf.lfClipPrecision	= CLIP_DEFAULT_PRECIS;
	lf.lfQuality		= ANTIALIASED_QUALITY;
	lf.lfPitchAndFamily	= VARIABLE_PITCH | FF_ROMAN;
	
	strcpy( lf.lfFaceName, LPCTSTR(strFontName) );
	
	font.CreateFontIndirect(&lf);

	// do not need to restore the old Device Context any more
	//CFont* pOldFont = pDC->SelectObject(&font);
	CGDISelectObject<CFont> tmpFont(pDC, &font);    // set the font

	CSize size;
	GetTextExtentPoint32(	pDC->m_hDC,				// handle to device context
							(LPCTSTR)strText,		// pointer to text string
							strText.GetLength(),	// number of characters in string
							&size);					// pointer to structure for string size

	CRect TextRect(CPoint(rect.left, rect.top), size);

	//pDC->SetTextColor( crFontColour );
	CGDISetTextColor tmpTextColor( pDC, crFontColour );
	pDC->DrawText(strText, &TextRect, DT_LEFT);		//  << The Text DrawText Command >>
	//pDC->DrawText(strText, &rect, DT_LEFT);		//  << The Text DrawText Command >>


	// do not need to reset the Device Context any more
	//pDC->SelectObject(pOldFont);
}

///////////////////////////////////////////////////////////////////////////////////////////////////
// 
///////////////////////////////////////////////////////////////////////////////////////////////////
void CGDIUtils::PaintHatch(CDC* pDC, CRect area, COLORREF crFG, COLORREF crBG)
{
	pDC->FillSolidRect(&area,crBG);

	pDC->SetBkColor( crBG );

	CBrush bgBrush;

	bgBrush.CreateHatchBrush( HS_CROSS, crFG );

	pDC->FillRect(&area,&bgBrush);
}


///////////////////////////////////////////////////////////////////////////////////////////////////
//   load a bitmap image
///////////////////////////////////////////////////////////////////////////////////////////////////
void CGDIUtils::LoadBitmap(CBitmap *pBitmap, const char *file)
{
    pBitmap->Detach();

    // load bitmap image from file
    HBITMAP hbitmap = (HBITMAP)::LoadImage(
        NULL, // handle of the instance that contains the image
        file, // name or identifier of image
        IMAGE_BITMAP,  // type of image-can also be IMAGE_CURSOR or IMAGE_ICON
        0, 0, // desired width and height
        LR_LOADFROMFILE);   // load from file

    // attach this bitmap object to our bitmap class
    pBitmap->Attach(hbitmap);
}


///////////////////////////////////////////////////////////////////////////////////////////////////
//   draw the bitmap image at its original size
///////////////////////////////////////////////////////////////////////////////////////////////////
void CGDIUtils::DrawBitmap(CDC *pDC, CBitmap *pBitmap, int X, int Y)
{
    CDC MemDC;
    MemDC.CreateCompatibleDC(NULL);
    MemDC.SelectObject(pBitmap);

    // obtain the size info of the image
    BITMAP BM;
    pBitmap->GetObject(sizeof(BM), &BM);

    pDC->BitBlt
        (X,Y,   // start from the left-top corner of the client
        BM.bmWidth, BM.bmHeight, // size of image
        &MemDC, // source dc for graphic data
        0,0,    // cut from the left-top corner of the image
        SRCCOPY);  // code for type of transfer
}


///////////////////////////////////////////////////////////////////////////////////////////////////
//   draw the bitmap image by stretching
///////////////////////////////////////////////////////////////////////////////////////////////////
void CGDIUtils::DrawBitmap(CDC *pDC, CBitmap *pBitmap, int X, int Y, double xScale, double yScale )
{
    CDC MemDC;
    MemDC.CreateCompatibleDC(NULL);
    MemDC.SelectObject(pBitmap);

    // obtain the size info of the image
    BITMAP BM;
    pBitmap->GetObject(sizeof(BM), &BM);

	int srcWidth = BM.bmWidth, srcHeight = BM.bmHeight ;
	int dstWidth = (int)( xScale * srcWidth ), dstHeight = (int)( yScale * srcHeight );
    pDC->StretchBlt
        ( X, Y, X+dstWidth, Y+dstHeight,
        &MemDC, 0, 0,srcWidth, srcHeight, SRCCOPY);
}


///////////////////////////////////////////////////////////////////////////////////////////////////
//   draw the bitmap image within a rectangle
///////////////////////////////////////////////////////////////////////////////////////////////////
void CGDIUtils::DrawBitmap(CDC *pDC, CBitmap *pBitmap, int X, int Y, int width, int height )
{
    CDC MemDC;
    MemDC.CreateCompatibleDC(NULL);
    MemDC.SelectObject(pBitmap);

    // obtain the size info of the image
    BITMAP BM;
    pBitmap->GetObject(sizeof(BM), &BM);

	int srcWidth = BM.bmWidth, srcHeight = BM.bmHeight ;
    pDC->StretchBlt
        ( X, Y, width, height,
        &MemDC, 0, 0,srcWidth, srcHeight, SRCCOPY);
}


///////////////////////////////////////////////////////////////////////////////////////////////////
//   draw a filled box
///////////////////////////////////////////////////////////////////////////////////////////////////
void	CGDIUtils::DrawFilledBox(CDC *pDC, CPoint center, int size, COLORREF color )
{
	pDC -> FillSolidRect(center.x-size, center.y-size, 2*size, 2*size, color);
}

void	CGDIUtils::DrawFilledBox(CDC *pDC, CRect &rect, COLORREF color )
{
	pDC -> FillSolidRect(rect.left, rect.top, rect.Width(), rect.Height(), color );
}

void	CGDIUtils::DrawFilledBox(CDC *pDC, CPoint pt1, CPoint pt2, COLORREF color )
{
	CBrush brush( color );
	CGDISelectObject<CBrush> tmpBrush(pDC, &brush);
	
	int xmin = std::min( pt1.x, pt2.x );
	int ymin = std::min( pt1.y, pt2.y );
	int xmax = std::max( pt1.x, pt2.x );
	int ymax = std::max( pt1.y, pt2.y );
	//pDC -> FillSolidRect(xmin, ymin, xmax-xmin, ymax-ymin, color );
	pDC -> Rectangle(xmin, ymin, xmax, ymax );
}

///////////////////////////////////////////////////////////////////////////////////////////////////
//   draw a filled circle
///////////////////////////////////////////////////////////////////////////////////////////////////
void	CGDIUtils::DrawFilledCircle(CDC *pDC, CPoint center, int size, COLORREF color )
{
	CBrush brush( color );
	CGDISelectObject<CBrush> tmpBrush(pDC, &brush);
	pDC->Ellipse(center.x-size, center.y-size, center.x+size, center.y+size );
}


///////////////////////////////////////////////////////////////////////////////////////////////////
// draw a circle without filling
///////////////////////////////////////////////////////////////////////////////////////////////////
void CGDIUtils::DrawEllipse( CDC *pDC, CPoint pt1, CPoint pt2 )
{
	//CBrush brush( color );
	//CGDISelectObject<CBrush> tmpBrush(pDC, &brush);

	pDC->SelectStockObject( NULL_BRUSH );
	//CGDISetBkMode tmp( pDC, TRANSPARENT );
	int xmin = std::min( pt1.x, pt2.x );
	int ymin = std::min( pt1.y, pt2.y );
	int xmax = std::max( pt1.x, pt2.x );
	int ymax = std::max( pt1.y, pt2.y );
	pDC->Ellipse( xmin, ymin, xmax, ymax );
}


///////////////////////////////////////////////////////////////////////////////////////////////////
//   draw grid lines
///////////////////////////////////////////////////////////////////////////////////////////////////
void CGDIUtils::DrawGrids(CDC *pDC, CRect rect, int offset)
{
	// set dotted pen
	CPen penDot;
	penDot.CreatePen(PS_DOT, g_Sizes[DHC_GRIDLINE], g_Colors[DHC_GRIDLINE]);

	CGDISelectObject<CPen> tmpPen(pDC, &penDot);

	//CPen* pOldPen = pDC->SelectObject(&penDot);

	int xmin = rect.left;
	int ymin = rect.top;
	int xmax = rect.right;
	int ymax = rect.bottom;

	// vertical lines
	for( int x = std::max(xmin, 0); x< xmax; x+=offset )
	{
		DrawLine( pDC, CPoint( x, ymin ), CPoint( x, ymax ) );
	}

	// horizontal lines
	for( int y = std::max(ymin, 0); y< ymax; y+=offset )
	{
		DrawLine( pDC, CPoint( xmin, y), CPoint( xmax, y) );
	}

	//pDC->SelectObject(pOldPen);
}



///////////////////////////////////////////////////////////////////////////////////////////////////
//  some non-GDI utils
///////////////////////////////////////////////////////////////////////////////////////////////////
BOOL	CGDIUtils::PointInPoint( double x, double y, double x1, double y1 )
{
	if( fabs( x-x1 )<=PIXELERROR && fabs( y-y1 )<=PIXELERROR )
		return TRUE;
	else
		return FALSE;
}


BOOL	CGDIUtils::PointOnLine( CPoint p, CPoint p1, CPoint p2, double &ratio )
{
	return PointOnLine( p.x, p.y, p1.x, p1.y, p2.x, p2.y, ratio );
}

BOOL	CGDIUtils::PointOnLine(int x, int y, int x1, int y1, int x2, int y2, double &ratio )
{
	return PointOnLine( (double)x,(double)y,(double)x1,(double)y1,(double)x2,(double)y2, ratio ); 
}

BOOL	CGDIUtils::PointOnLine(double x, double y, double x1, double y1, double x2, double y2, double &ratio )
{
    double d1 = sqrt((x1-x)*(x1-x)+(y1-y)*(y1-y));
    double d2 = sqrt((x2-x)*(x2-x)+(y2-y)*(y2-y));
    double d =  sqrt((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1));

    if( fabs(d1+d2-d) <= PIXELERROR )
	{
		assert( d != 0 );
		ratio = d1/d;
		return TRUE;
	}
    else 
		return FALSE;
}

CRect	CGDIUtils::UnionRect( CRect &rect1, CRect &rect2 )
{
	CRect res;
	res.left = std::min( rect1.left, rect2.left );
	res.right = std::max( rect1.right, rect2.right );
	res.top = std::min( rect1.top, rect2.top );
	res.bottom = std::max( rect1.bottom, rect2.bottom );

	return res;
}


CPoint	CGDIUtils::GetNearestCorner( CPoint pt, CRect rect )
{
	assert( rect.PtInRect( pt ) );

	int x = pt.x, y = pt.y ;

	if( (x-rect.left)<=PIXELERROR )
		x = rect.left;
	if( (rect.right-x)<=PIXELERROR )
		x = rect.right;
	if( (y-rect.top)<=PIXELERROR )
		y = rect.top;
	if( (rect.bottom-y)<=PIXELERROR )
		y = rect.bottom;

	return CPoint(x,y);
}


BOOL	CGDIUtils::RectInRect( CRect &rect1, CRect &rect2 )
{
	if( InInterval( rect1.left, rect2.left, rect2.right ) &&
		InInterval( rect1.right, rect2.left, rect2.right ) &&
		InInterval( rect1.top, rect2.top, rect2.bottom ) &&
		InInterval( rect1.bottom, rect2.top, rect2.bottom ) )
		return TRUE;
	else
		return FALSE;
}

BOOL	CGDIUtils::InInterval( int x, int x0, int x1 )
{
	if( x>=x0 && x<=x1 )
		return TRUE;
	else
		return FALSE;
}


///////////////////////////////////////////////////////////////////////////////////////////////////
//  some math utils
///////////////////////////////////////////////////////////////////////////////////////////////////
// the distance between two ponit p0 and p1
double CGDIUtils::Distance(const CVertex &p0, const CVertex &p1)
{
	return sqrt( (p1.x - p0.x) * (p1.x - p0.x) + (p1.y - p0.y) * (p1.y - p0.y) );
}

// calculate the angle between vector p0 and p1
// there must be some implicit error for these functions... to be checked...
double CGDIUtils::Angle(const CVertex &p0, const CVertex &p1)
{
	if (p0 == p1 || p0.IsZero() || p1.IsZero() )
	{
		return 0;
	}
	if (p0 * p1 == 0)
	{
		return Pi / 2;
	}

	return atan((p0 ^ p1) / (p0 * p1));
	//return atan( fabs(p0 ^ p1) / (p0 * p1)) );
}


// calculate the turning angle between vector p0p1 to vector p2p3
double CGDIUtils::CalcAngle(const CVertex &p0, const CVertex &p1, const CVertex &p2, const CVertex &p3)
{
	CVertex a = p1 - p0;
	CVertex b = p3 - p2;

	if(a.Norm() * b.Norm() < EPS_6)
	{
		return 0;
	}

	double angle = Angle(a, b);

	double sin = (a ^ b) / (a.Norm() * b.Norm());
	double cos = (a * b) / (a.Norm() * b.Norm());

	// make sure in the range of [0,1]
	if( sin<-1.0f ) 
		sin = -1.0;
	if( sin>1.0f ) 
		sin = 1.0;
	if( cos<-1.0f ) 
		cos = -1.0;
	if( cos>1.0f ) 
		cos = 1.0;


	if (fabs(sin) <= EPS_6 && fabs(1.0 - cos) <= EPS_6)	
		//sin == 0 ,cos == 1
	{
		return 0;
	}

	if (fabs(1.0 - sin) <= EPS_6 && fabs(cos) <= EPS_6) 
		// sin == 1, cos == 0
	{
		return Pi / 2;
	}

	if (fabs(sin) <= EPS_6 && fabs(-1 - cos) <= EPS_6)	
		//sin == 0, cos == -1
	{
		return Pi;
	}

	if (fabs(-1 - sin) <= EPS_6 && fabs(cos) <= EPS_6)	
		//sin == -1, cos == 0
	{
		return - Pi / 2;
	}


	if (sin > 0 && cos > 0)
	{
		assert(angle > 0);
		return angle;
	}

	if (sin > 0 && cos < 0)
	{
		assert(angle < 0);
		return angle + Pi;
	}

	if (sin < 0 && cos < 0)
	{
		assert(angle > 0);
		return angle - Pi;
	}

	if (sin < 0 && cos > 0)
	{
		assert(angle < 0);
		return angle;
	}

	assert(false);
	return 0;
}
